package com.leap.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class JwtUtils {

    @Value("${spring.security.jwt.user-key}")
    String userKey;

    @Value("${spring.security.jwt.user-expire}")
    int userExpire;

    @Resource
    RedisTemplate template;

    public String createJwt(Long id, String openid) {
        Algorithm algorithm = Algorithm.HMAC256(userKey);
        Date expire = this.expireTime();
        return JWT.create()
                .withJWTId(UUID.randomUUID().toString())
                .withClaim("id", id)
                .withClaim("openid", openid)
                .withExpiresAt(expire)
                .withIssuedAt(new Date())
                .sign(algorithm);
    }

    public String createAdminJwt(String username,String role) {
        Algorithm algorithm = Algorithm.HMAC256(userKey);
        Date expire = this.expireTime();
        ArrayList<String> roles = new ArrayList<>();
        roles.add(role);
        return JWT.create()
                .withJWTId(UUID.randomUUID().toString())
                .withClaim("username", username)
                .withClaim("role",roles )
                .withExpiresAt(expire)
                .withIssuedAt(new Date())
                .sign(algorithm);
    }

    public Date expireTime() {
        Calendar calendar= Calendar.getInstance();
        calendar.add(Calendar.HOUR, userExpire * 24);
        return calendar.getTime();
    }

    public DecodedJWT resolveJwt(String headerToken) {
        String token = this.convertToken(headerToken);
        if (token == null)
            return null;
        Algorithm algorithm = Algorithm.HMAC256(userKey);
        JWTVerifier jwtVerifier = JWT.require(algorithm).build();
        try {
            DecodedJWT verify = jwtVerifier.verify(token);
            if (this.isInvalidToken(verify.getId()))
                return null;
            Date expiresAt = verify.getExpiresAt();
            return new Date().after(expiresAt) ? null : verify;
        }catch (JWTVerificationException e) {
            return null;
        }
    }

    public boolean invalidateJwt(String headerToken) {
        String token = this.convertToken(headerToken);
        if (token ==null) return false;
        Algorithm algorithm = Algorithm.HMAC256(userKey);
        JWTVerifier jwtVerifier = JWT.require(algorithm).build();
        try {
            DecodedJWT jwt = jwtVerifier.verify(token);
            String id = jwt.getId();
            return deleteToken(id,jwt.getExpiresAt());
        }catch (JWTVerificationException e){
            return false;
        }
    }

    private boolean deleteToken(String uuid, Date time) {
        if (this.isInvalidToken(uuid))
            return false;
        Date now = new Date();
        long expire = Math.max(time.getTime() - now.getTime(),0);
        template.opsForValue().set(Const.JWT_BLACK_LIST + uuid, "",expire, TimeUnit.MILLISECONDS);
        return true;
    }

    private boolean isInvalidToken(String uuid) {
        String tokenKey = Const.JWT_BLACK_LIST + uuid;
        return template.hasKey(tokenKey);
    }

    private String convertToken(String headerToken){
        if (headerToken == null || !headerToken.startsWith("Bearer ")) {
            return null;
        }
        return headerToken.substring(7);
    }


    public String toOpenid(DecodedJWT jwt) {
        Map<String, Claim> claims = jwt.getClaims();
        if(claims.containsKey("openid")){
            return claims.get("openid").asString();
        }else
            return null;
    }

    public String toUsername(DecodedJWT jwt) {
        Map<String, Claim> claims = jwt.getClaims();
        if(claims.containsKey("username")){
            return claims.get("username").asString();
        }else
            return null;
    }

    public List<String> toAuthorities(DecodedJWT jwt) {
        Map<String, Claim> claims = jwt.getClaims();
        if (claims.containsKey("role")){
            return claims.get("role").as(ArrayList.class);
        }else
            return null;
    }

    public Long getAccountIdByToken(String headerToken) {
        DecodedJWT jwt = this.resolveJwt(headerToken);
        if (jwt == null){
            throw new UsernameNotFoundException("Unknown Token");
        }
        Map<String, Claim> claims = jwt.getClaims();
        Long id = 0L;
        if (claims.containsKey("id")){
            id = claims.get("id").asLong();
        }
        return id;
    }


}
